/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "bus_center.h"

#include <stdbool.h>

#include "common.h"
#include "softbus_adapter_crypto.h"
#include "softbus_bus_center.h"
#include "softbus_utils.h"

#define SHORT_UDID_HASH_LEN 4
#define SHORT_DEVID_LEN 8
#define SHIFTLNNGEAR_CALLER_ID_LEN 128
#define NUM_BUF_SIZE 4

static sem_t g_joinLnnSem;
static sem_t g_leaveLnnSem;
static char const *g_pkgName = "com.softbus_tool.test";

static void OnJoinLNNResultCb(ConnectionAddr *addr, const char *networkId, int32_t retCode)
{
    printf(">>>OnJoinLNNResult networkId = %s, retCode = %d.\n", networkId, retCode);
    sem_post(&g_joinLnnSem);
}

static void OnLeaveLNNDone(const char *networkId, int retCode)
{
    printf(">>>OnLeaveLNNDone networkId = %s, retCode = %d.\n", networkId, retCode);
    sem_post(&g_leaveLnnSem);
}

void BC_JoinLNN(void)
{
    ConnectionAddr addr;
    (void)memset_s(&addr, sizeof(ConnectionAddr), 0, sizeof(ConnectionAddr));
    addr.type = GetInputNumber("Please input connection type(0 - WiFi, 1 - BR, 2 - BLE, 3 - ETH):");
    switch (addr.type) {
        case CONNECTION_ADDR_WLAN:
        case CONNECTION_ADDR_ETH:
            GetInputString("Please input ip:", addr.info.ip.ip, IP_STR_MAX_LEN);
            addr.info.ip.port = GetInputNumber("Please input port:");
            break;
        case CONNECTION_ADDR_BR:
            GetInputString("Please input BT mac:", addr.info.br.brMac, BT_MAC_LEN);
            break;
        case CONNECTION_ADDR_BLE:
            GetInputString("Please input BLE mac:", addr.info.ble.bleMac, BT_MAC_LEN);
            break;
        default:
            printf("unkown connection type, input: %d", addr.type);
            return;
    }
    sem_init(&g_joinLnnSem, 0, 0);
    int ret = JoinLNN(PKG_NAME, &addr, OnJoinLNNResultCb);
    if (ret != 0) {
        printf("JoinLNN fail, ret = %d\n", ret);
        return;
    }
    sem_wait(&g_joinLnnSem);
    sem_destroy(&g_joinLnnSem);
}

void BC_LeaveLNN(void)
{
    char networkId[NETWORK_ID_BUF_LEN];
    GetInputString("Please input network Id:", networkId, NETWORK_ID_BUF_LEN);
    sem_init(&g_leaveLnnSem, 0, 0);
    int ret = LeaveLNN(PKG_NAME, networkId, OnLeaveLNNDone);
    if (ret != 0) {
        printf("LeaveLNN fail, ret = %d\n", ret);
        return;
    }
    sem_wait(&g_leaveLnnSem);
    sem_destroy(&g_leaveLnnSem);
}

static void PrintDeviceInfo(const NodeBasicInfo *info)
{
    printf(">>>DeviceName: %s\n", info->deviceName);
    printf(">>>DeviceNetworkId: %s\n", info->networkId);
    unsigned char udid[UDID_BUF_LEN] = {0};
    int ret = GetNodeKeyInfo(PKG_NAME, info->networkId, NODE_KEY_UDID, udid, UDID_BUF_LEN);
    if (ret != 0) {
        printf("GetNodeKeyInfo NODE_KEY_UDID fail, ret = %d.\n", ret);
        return;
    }
    printf(">>>Udid: %s\n", udid);
    unsigned char masterUdid[UDID_BUF_LEN] = {0};
    ret = GetNodeKeyInfo(PKG_NAME, info->networkId, NODE_KEY_MASTER_UDID, masterUdid, UDID_BUF_LEN);
    if (ret != 0) {
        printf("GetNodeKeyInfo NODE_KEY_MASTER_UDID fail, ret = %d.\n", ret);
    }
    printf(">>>MasterUdid: %s\n", masterUdid);
    unsigned char uuid[UUID_BUF_LEN] = {0};
    ret = GetNodeKeyInfo(PKG_NAME, info->networkId, NODE_KEY_UUID, uuid, UUID_BUF_LEN);
    if (ret != 0) {
        printf("GetNodeKeyInfo NODE_KEY_UUID fail, ret = %d.\n", ret);
        return;
    }
    printf(">>>Uuid: %s\n", uuid);
    int32_t netCapacity = 0;
    ret = GetNodeKeyInfo(PKG_NAME, info->networkId, NODE_KEY_NETWORK_CAPABILITY, (uint8_t *)&netCapacity, NUM_BUF_SIZE);
    if (ret != 0) {
        printf("GetNodeKeyInfo NODE_KEY_NETWORK_CAPABILITY fail, ret = %d.\n", ret);
        return;
    }
    printf(">>>NetCapacity: %d\n", netCapacity);
    int32_t netType = 0;
    ret = GetNodeKeyInfo(PKG_NAME, info->networkId, NODE_KEY_NETWORK_TYPE, (uint8_t *)&netType, NUM_BUF_SIZE);
    if (ret != 0) {
        printf("GetNodeKeyInfo NODE_KEY_NETWORK_TYPE fail, ret = %d.\n", ret);
        return;
    }
    printf(">>>NetType: %d\n", netType);

    /* calculate udid hash for ble discovery */
    unsigned char udidHash[UDID_HASH_LEN] = {0};
    char shortDevId[SHORT_DEVID_LEN + 1] = {0};
    (void)SoftBusGenerateStrHash(udid, strlen((const char*)udid) + 1, udidHash);
    (void)ConvertBytesToHexString(shortDevId, sizeof(shortDevId), udidHash, SHORT_UDID_HASH_LEN);
    printf(">>>ShortDevId: %s\n", shortDevId);
}

void BC_GetOnlineDeviceInfo(void)
{
    int32_t onlineNum;
    NodeBasicInfo *info = NULL;
    int ret = GetAllNodeDeviceInfo(PKG_NAME, &info, &onlineNum);
    if (ret != 0) {
        printf("GetAllNodeDeviceInfo fail, ret = %d.\n", ret);
        return;
    }
    printf("online devices num: %d\n", onlineNum);
    for (int i = 0; i < onlineNum; i++) {
        printf(">>>Index: %d\n", i);
        PrintDeviceInfo(&info[i]);
    }
    FreeNodeInfo(info);
}

void BC_GetLocalDeviceInfo(void)
{
    NodeBasicInfo info;
    int ret = GetLocalNodeDeviceInfo(PKG_NAME, &info);
    if (ret != 0) {
        printf("GetLocalNodeDeviceInfo fail, ret = %d.\n", ret);
        return;
    }
    PrintDeviceInfo(&info);
}

static void BC_ShiftLnnGearSetParam(int32_t cycle, int32_t duration, int32_t wakeupFlag, GearMode *mode)
{
    switch (cycle) {
        case 0:
            mode->cycle = HIGH_FREQ_CYCLE;
            break;
        case 1:
            mode->cycle = MID_FREQ_CYCLE;
            break;
        case 2:
            mode->cycle = LOW_FREQ_CYCLE;
            break;
        default:
            mode->cycle = LOW_FREQ_CYCLE;
            break;
    }
    switch (duration) {
        case 0:
            mode->duration = DEFAULT_DURATION;
            break;
        case 1:
            mode->duration = NORMAL_DURATION;
            break;
        case 2:
            mode->duration = LONG_DURATION;
            break;
        default:
            mode->duration = LONG_DURATION;
            break;
    }
    switch (wakeupFlag) {
        case 0:
            mode->wakeupFlag = false;
            break;
        case 1:
            mode->wakeupFlag = true;
            break;
        default:
            mode->wakeupFlag = false;
            break;
    }
}

void BC_ShiftLnnGear(void)
{
    int32_t ret;
    char networkId[NETWORK_ID_BUF_LEN] = {0};
    char callerId[SHIFTLNNGEAR_CALLER_ID_LEN] = {0};
    GearMode mode = {0};

    GetInputString("Please input callerId (maxLen 128):", callerId, SHIFTLNNGEAR_CALLER_ID_LEN);
    int32_t wakeupFlag = GetInputNumber("Please input wakeupFlag(0 - false, 1 - true):");
    int32_t cycle = GetInputNumber("Please input cycle(0 - HIGH 30s, 1 - MID 60s, 2 - LOW 5min):");
    int32_t duration = GetInputNumber("Please input duration(0 - DEFAULT 60s, 1 - NORMAL 10min, 2 - LONG 30min):");
    BC_ShiftLnnGearSetParam(cycle, duration, wakeupFlag, &mode);
    int32_t isInputNetworkId = GetInputNumber("Need input network Id?(0 - false, 1 - true):");
    if (isInputNetworkId == 0) {
        ret = ShiftLNNGear(g_pkgName, callerId, NULL, &mode);
    } else {
        GetInputString("Please input network Id:", networkId, NETWORK_ID_BUF_LEN);
        ret = ShiftLNNGear(g_pkgName, callerId, networkId, &mode);
    }
    if (ret != 0) {
        printf("ShiftLNNGear fail, ret = %d.\n", ret);
        return;
    }
    printf("ShiftLNNGear success.\n");
}

static sem_t g_publishSem;
static void OnPublishResult(int publishId, PublishResult reason)
{
    if (reason != PUBLISH_LNN_SUCCESS){
        Logf(INFO, ">>>publish failed: publishId=%d, result=%d", publishId, reason);
    } else {
        Logf(INFO, ">>>publish success: publishId=%d", publishId);
    }
    
    sem_post(&g_publishSem);
}

void BC_PublishLNN(void)
{
    sem_init(&g_publishSem, 0, 0);
    PublishInfo info = {
        .publishId = 1,
        .medium = AUTO,
        .mode = DISCOVER_MODE_ACTIVE,
        .freq = MID,
        .capability = "dvKit",
        .capabilityData = (unsigned char *)"capdata1",
        .dataLen = 9,
        .ranging = true,
    };
    info.publishId = GetInputNumber("Please input publish id:");
    info.medium = GetInputNumber("Please input publish medium(0 - AUTO, 1 - BLE, 2 - COAP):");
    int choice = GetInputNumber("Please input publish mode(0 - DISCOVER_MODE_PASSIVE, 1 - DISCOVER_MODE_ACTIVE):");
    if (choice == 0) {
        info.mode = DISCOVER_MODE_PASSIVE;
    } else if (choice == 1) {
        info.mode = DISCOVER_MODE_ACTIVE;
    } else {
        Logf(WARN, "unknown mode choice %d, use default DISCOVER_MODE_ACTIVE", choice);
    }
    choice = GetInputNumber("Please input publish ranging(0 - false, 1 - true)");
    if (choice == 0) {
        info.ranging = false;
    } else if (choice == 1) {
        info.ranging = true;
    } else {
        Logf(WARN, "unknown mode choice %d, use default 'true'", choice);
    }
    
    IPublishCb cb = {
        .OnPublishResult = OnPublishResult,
    };
    int ret = PublishLNN(PKG_NAME, &info, &cb);
    if (ret != 0) {
        Logf(ERROR, "PublishLNN fail, ret = %d", ret);
        sem_destroy(&g_publishSem);
        return;
    }
    sem_wait(&g_publishSem);
    sem_destroy(&g_publishSem);
}

void BC_StopPublishLNN(void)
{
    int publishId = GetInputNumber("Please input publish id:");
    int ret = StopPublishLNN(PKG_NAME, publishId);
    if (ret != 0) {
        Logf(ERROR, "StopPublishLNN fail, ret = %d", ret);
    }
}

static sem_t g_refreshSem;
static bool g_showDevice = false;
static bool g_refreshSucc = false;
static void OnDiscoverResult(int refreshId, RefreshResult reason)
{
    if (reason != REFRESH_LNN_SUCCESS) {
        Logf(ERROR, ">>>RefreshLNN failed: refreshId=%d, result=%d", refreshId, reason);
    } else {
        Logf(INFO, ">>>RefreshLNN success: refreshId=%d", refreshId);
        g_refreshSucc = true;
    }
    sem_post(&g_refreshSem);
}

static void OnDeviceFound(const DeviceInfo *device)
{
    if (device == NULL || !g_showDevice) {
        printf("Device found but not show: device is null or no need to show\n");
        return;
    }

    int32_t i = 0;
    while (i < device->addrNum) {
        switch (device->addr[i].type) {
            case CONNECTION_ADDR_BLE:
                Logf(INFO, ">>>OnDeviceFound: BLE MAC - %s  |  DevId - %s", device->addr[i].info.ble.bleMac, device->devId);
                break;
            case CONNECTION_ADDR_BR:
                Logf(INFO, ">>>OnDeviceFound: BR MAC - %s  |  DevId - %s", device->addr[i].info.br.brMac, device->devId);
                break;
            case CONNECTION_ADDR_ETH:
            case CONNECTION_ADDR_WLAN:
                Logf(INFO, ">>>OnDeviceFound: IP ADDR:Port - %s:%d  |  DevId - %s", device->addr[i].info.ip.ip,
                    device->addr[i].info.ip.port, device->devId);
                break;
            default:
                Logf(ERROR, ">>>OnDeviceFound: unknown device type, %d", device->addr[i].type);
                break;
        }
        i++;
    }
    Logf(INFO, ">>>OnDeviceFound: range - %d  |  DevId - %s", device->range, device->devId);
}

void BC_RefreshLNN(void)
{
    sem_init(&g_refreshSem, 0, 0);
    SubscribeInfo info = {
        .subscribeId = 1,
        .medium = AUTO,
        .mode = DISCOVER_MODE_ACTIVE,
        .freq = MID,
        .isSameAccount = false,
        .isWakeRemote = false,
        .capability = "dvKit",
        .capabilityData = (unsigned char *)"capdata1",
        .dataLen = 9,
    };
    info.subscribeId = GetInputNumber("Please input subscribe id:");
    info.medium = GetInputNumber("Please input publish medium(0 - AUTO, 1 - BLE, 2 - COAP):");
    int choice = GetInputNumber("Please input publish mode(0 - DISCOVER_MODE_PASSIVE, 1 - DISCOVER_MODE_ACTIVE):");
    if (choice == 0) {
        info.mode = DISCOVER_MODE_PASSIVE;
    } else if (choice == 1) {
        info.mode = DISCOVER_MODE_ACTIVE;
    } else {
        Logf(WARN, "unknown mode choice %d, use default DISCOVER_MODE_ACTIVE", choice);
    }

    IRefreshCallback cb = {
        .OnDeviceFound = OnDeviceFound,
        .OnDiscoverResult = OnDiscoverResult,
    };
    
    g_showDevice = true;
    g_refreshSucc = false;
    int ret = RefreshLNN(PKG_NAME, &info, &cb);
    if (ret != 0) {
        printf("RefreshLNN fail, ret = %d.\n", ret);
        sem_destroy(&g_refreshSem);
        return;
    }
    sem_wait(&g_refreshSem);
    if (g_refreshSucc) {
        WaitInputEnter("Found Devices shown below, please input ENTER key to break.\n");
        g_showDevice = false;
    }
    sem_destroy(&g_refreshSem);
}

void BC_StopRefreshLNN(void)
{
    int subscribeId = GetInputNumber("Please input subscribe id:");
    int ret = StopRefreshLNN(PKG_NAME, subscribeId);
    if (ret != 0) {
        Logf(ERROR, "StopRefreshLNN fail, ret = %d.\n", ret);
    }
}
